
import Foundation

class OspnImService {
    var mHostIp = ""
    var mOsnId = ""
    var mOsnKey = ""
    var mShadowId = ""
    var mShadowKey = ""
    var mServiceId = ""
    var mDeviceId = ""
    var mAesKey = ""
    var mToken:Int64 = 0
    var mNeedLogin = true
    var mSyncing = false
    
    init(){
        initImService("")
    }
    func initImService(_ hostIp:String){
        let defaults = UserDefaults.standard
        mHostIp = hostIp.isEmpty ? defaults.string(forKey: "ospn_HostIp") ?? "" : hostIp
        mOsnId = defaults.string(forKey: "ospn_OsnId") ?? ""
        mOsnKey = defaults.string(forKey: "ospn_OsnKey") ?? ""
        mShadowId = defaults.string(forKey: "ospn_ShadowId") ?? ""
        mShadowKey = defaults.string(forKey: "ospn_ShadowKey") ?? ""
        mServiceId = defaults.string(forKey: "ospn_ServiceId") ?? ""
        mDeviceId = defaults.string(forKey: "ospn_DeviceId") ?? ""
        mAesKey = defaults.string(forKey: "ospn_AesKey") ?? ""
        mToken = Int64(defaults.integer(forKey: "ospn_Token"))
        
        if mDeviceId.isEmpty{
            mDeviceId = UUID().uuidString
            defaults.set(mDeviceId, forKey: "ospn_DeviceId")
        }
        if !mOsnId.isEmpty{
            _ = gosnSqlite.initSqlite(mOsnId)
        }
        gnetService.mStatusCallback = netStatus
        gnetService.mCallback = OspnImService.shandleMessage
        gnetService.initService(mHostIp)
        gidService.initService()
    }
    func isLogined()->Bool{
        if !mOsnId.isEmpty {
            return true
        }
        let defaults = UserDefaults.standard
        return defaults.string(forKey: "ospn_OsnId") != nil
    }
    func getLoginInfo(_ user:String, _ key:String) async -> [String:Any]? {
        let request:[String:Any] = [
            "command": "GetLoginInfo",
            "from": user
        ]
        let (json, error) = await gnetService.sendPackage(package: request, timeOut: 10000)
        guard let json = json, error == nil else {
            print("getLoginInfo \(String(describing: error))")
            return nil
        }
        guard let content = json["content"] as? String,
              let json = OsnUtils.toJson(content) else {
            print("no content \(json)")
            return nil
        }
        return OsnUtils.takeMessage(json: json, key: key, osnID: user)
    }
    func loginWithShadow(challenge: Int) async -> Bool {
        mToken = OsnUtils.getTimeStamp()
        let calc = mOsnId+String(challenge)+String(mToken)
        let hash = EcUtils.osnHash(data: calc.data(using: .utf8) ?? Data())
        guard let sign = EcUtils.osnSign(priKey: mShadowKey, data: hash.data(using: .utf8) ?? Data()) else {return false}
        let data:[String:Any] = [
            "pubKey": mShadowId,
            "token": mToken,
            "sign": sign
        ]
        
        print("\(data)")
        guard let message = OsnUtils.makeMessage(command: "LoginV2", from: mOsnId, to: mServiceId, data: data, key: mOsnKey) else {return false}
        print("\(message)")
        let (json, error) = await gnetService.sendPackage(package: message)
        guard let json = json, error == nil else {
            print("\(String(describing: error))")
            return false
        }
        guard let data = OsnUtils.takeMessage(json: json, key: mOsnKey, osnID: mOsnId) else {return false}
        print("\(data)")
        if isSuccess(data) {
            guard let data = OsnUtils.takeMessage(json: data, key: mOsnKey, osnID: mOsnId) else {return false}
            print("\(data)")
            guard let aeskey = data["aesKey"] as? String else {return false}
            mAesKey = aeskey
            mNeedLogin = false
            
            let defaults = UserDefaults.standard
            defaults.set(mHostIp, forKey: "ospn_HostIp")
            defaults.set(mOsnId, forKey: "ospn_OsnId")
            defaults.set(mOsnKey, forKey: "ospn_OsnKey")
            defaults.set(mShadowId, forKey: "ospn_ShadowId")
            defaults.set(mShadowKey, forKey: "ospn_ShadowKey")
            defaults.set(mServiceId, forKey: "ospn_ServiceId")
            //defaults.set(mDeviceId, forKey: "ospn_DeviceId")
            defaults.set(mAesKey, forKey: "ospn_AesKey")
            defaults.set(mToken, forKey: "ospn_Token")

            Task {
                await syncUser()
                await syncFriend()
                await syncGroup()
            }
            print("im login done")
            return true
        }
        mNeedLogin = true
        return false
    }
    func getLoginDetail(loginData:[String:Any]) -> Bool {
        guard let userInfo = loginData["userInfo"] as? [String:Any],
              let privateKey = userInfo["privateKey"] as? String,
              let osnId = userInfo["osnId"] as? String,
              let loginConfig = loginData["loginConfig"] as? [String:Any],
              let hostIp = loginConfig["hostIp"] as? String else {
            return false
        }
        ghttpService.setConfig(loginData)
        let keys = privateKey.split(separator: "-").map({String($0)})
        if keys.count < 3 {
            return false
        }
        guard let shadowKeyData = OsnUtils.b64Decode(keys[2]) else {return false}
        guard let shadowAddr = EcUtils.genAddress(priKey: shadowKeyData),
              let shadowPrivate = EcUtils.genPrivateKeyStr(priKey: shadowKeyData) else {return false}

        mOsnId = osnId
        mOsnKey = String(keys[1])
        mShadowId = shadowAddr
        mShadowKey = shadowPrivate
        mHostIp = hostIp
        
        return true
    }
    func loginWithConfig(_ loginData:[String:Any]) async ->OspnError? {
        guard getLoginDetail(loginData: loginData) else {return OspnError.Config}

        if(!gosnSqlite.initSqlite(mOsnId)){
            print("initSqlite error")
            return OspnError.Sqlite
        }
        gnetService.initService(mHostIp)
        if(!gnetService.waitService()){
            print("waitService time out")
            return OspnError.Timeout
        }
        guard let loginInfo = await getLoginInfo(mOsnId, mOsnKey) else {return OspnError.Config}
        print("\(loginInfo)")
        guard let serviceId = loginInfo["serviceID"] as? String,
              let challenge = loginInfo["challenge"] as? Int else {return OspnError.Config}
        mServiceId = serviceId
        if await loginWithShadow(challenge: challenge) {
            return nil
        }
        return OspnError.Custom(msg: "im login failure")
    }
    func netStatus(_ status: Bool){
        if(!status){
            mNeedLogin = true
            return
        }
        if(mNeedLogin){
            Task { await reLogin() }
        }
    }
    func reLogin() async {
        if(mOsnId.isEmpty || !mNeedLogin){
            return
        }
        let challenge = OsnUtils.getTimeStamp()
        let json:[String:Any] = [
            "command": "Login",
            "type": "osn",
            "user": mOsnId,
            "token": mToken,
            "platform": "ios",
            "deviceID": mDeviceId,
            "ver": "2",
            "challenge": OsnUtils.aesEncrypt(dataStr: String(challenge), keyStr: mAesKey) ?? "",
        ]
        print("relogin \(json)")
        let (res, error) = await imRequestRaw(json)
        if error != nil {
            print("\(error!)")
            if error?.description == "10032:token error" {
                geventService.post(OsnEvent.KickOffAction, [:])
            }
            return
        }
        guard let content = res!["content"] as? String, let data = OsnUtils.toJson(content) else {return}
        guard let data = data["data"] as? String else {return}
        guard let data = OsnUtils.b64Decode(data) else {return}
        let key = OsnUtils.sha256(data: mAesKey.data(using: .utf8)!)
        guard let content = OsnUtils.aesDecrypt(data: data, key: key) else {return}
        guard let json = OsnUtils.fromData(data: content) else {return}
        mAesKey = json["aesKey"] as? String ?? ""
        UserDefaults.standard.set(mAesKey, forKey: "AesKey")
        mNeedLogin = false
        Task { await syncUncompleteMessages() }
    }
    func logout(){
        gnetService.stopService()
        geventService.clear()
        
        let defaults = UserDefaults.standard
        defaults.removeObject(forKey: "ospn_HostIp")
        defaults.removeObject(forKey: "ospn_OsnId")
        defaults.removeObject(forKey: "ospn_OsnKey")
        defaults.removeObject(forKey: "ospn_ShadowId")
        defaults.removeObject(forKey: "ospn_ShadowKey")
        defaults.removeObject(forKey: "ospn_ServiceId")
        //defaults.removeObject(forKey: "ospn_DeviceId")
        defaults.removeObject(forKey: "ospn_AesKey")
        defaults.removeObject(forKey: "ospn_Token")

        mHostIp = ""
        mOsnId = ""
        mOsnKey = ""
        mShadowId = ""
        mShadowKey = ""
        mServiceId = ""
        //mDeviceId = ""
        mAesKey = ""
        mToken = 0
        mNeedLogin = true
        mSyncing = false
    }
    func syncUser() async {
        _ = await gosnUser.fetchUserInfo(mOsnId)
    }
    func syncFriend() async {
        let friendIdList = await gosnFriend.fetchFriendList()
        for friend in friendIdList {
            let info = gosnFriend.getFriendInfo(friend)
            if info == nil {
                _ = await gosnFriend.fetchFriendInfo(friend)
                _ = await gosnUser.fetchUserInfo(friend)
            }
        }
    }
    func syncGroup() async {
        let (groups, err) = await ghttpService.listGroup()
        if err != nil {
            print("syncGroup \(err!)")
            return
        }
        for dic in groups! {
            let groupId = dic["groupOsnId"] as? String
            if gosnGroup.getGroupInfo(groupId!) == nil {
                _ = await gosnGroup.fetchGroupInfo(groupId!)
            }
        }
    }
    func syncUncompleteMessages() async {
        if(mSyncing){
            return
        }
        mSyncing = true

        var mid = 0
        while(!mOsnId.isEmpty && !mNeedLogin){
            let data:[String:Any] = ["id":mid, "count":30]
            if let res = await imRequest("GetUncomplete", mServiceId, data),
               let result = getResult(res) {
                
                let messages = result["messages"] as? [String] ?? []
                if messages.isEmpty{
                    mSyncing = false
                    sleep(1)
                    break
                }
                mid = result["lastId"] as? Int ?? 0
                for msg in messages {
                    if let json = OsnUtils.toJson(msg) {
                        await gimService.handleMessage(json)
                    }
                }
                
//                if messages.count == 0 {
//                    break
//                }
            }
            sleep(1)
            
//            usleep(100000)
        }
    }
    func imRequestWithError(_ command:String, _ to:String, _ data:[String:Any]?, _ timeOut:Int = 5000) async -> ([String:Any]?,OspnError?) {
        guard let json = OsnUtils.makeMessage(command: command, from: mOsnId, to: to, data: data, key: mOsnKey) else {
            return (nil, OspnError.Encode)
        }
        return await gnetService.sendPackage(package: json)
    }
    func imRequest(_ command:String, _ to:String, _ data:[String:Any]?, _ timeOut:Int = 5000) async -> [String:Any]? {
        let (res, err) = await imRequestWithError(command, to, data, timeOut)
        return (err != nil) ? nil : res
    }
    func imRequestServiceWithError(_ command:String, _ data:[String:Any]?, _ timeOut:Int = 5000) async -> ([String:Any]?,OspnError?) {
        return await imRequestWithError(command, mServiceId, data, timeOut)
    }
    func imRequestService(_ command:String, _ data:[String:Any]?, _ timeOut:Int = 5000) async -> [String:Any]? {
        return await imRequest(command, mServiceId, data, timeOut)
    }
    func imRequestRaw(_ data:[String:Any], _ timeOut:Int = 5000) async -> ([String:Any]?,OspnError?) {
        return await gnetService.sendPackage(package: data)
    }
    func imRequestNoWait(_ data:[String:Any], _ timeOut:Int = 5000) async {
        gnetService.sendOnly(package: data)
    }
    func getResult(_ json:[String:Any]) -> [String:Any]? {
        return OsnUtils.takeMessage(json: json, key: mOsnKey, osnID: mOsnId)
    }
    func completeMessage(_ info:[String:Any]) async {
        var hash = info["receive_hash"] as? String
        if hash == nil{
            hash = info["hash"] as? String
        }
        if hash == nil{
            return
        }
        let data:[String:Any] = [
            "hash": hash!,
            "sign": EcUtils.osnSign(priKey: mOsnKey, data: hash!.data(using: .utf8)!) ?? "",
        ]
        let json = OsnUtils.wrapMessage(command: "Complete", from: mOsnId, to: info["from"] as? String ?? "", data: data, key: mOsnKey)
        gnetService.sendOnly(package: json ?? [:])
    }

    static func shandleMessage(_ json:[String:Any]) async {
        await gimService.handleMessage(json)
    }
    func handleMessage(_ json:[String:Any]) async {
        guard let data = getResult(json) else {return}
        print("json: \(data)")
        switch json["command"] as? String ?? "" {
        case "RejectMember", "MessageRead", "Replay", "RejectFriend":
            await completeMessage(json)
            break
        case "AddFriend":
            await handleAddFriend(json, data)
            await completeMessage(json)
            break
        case "AgreeFriend":
            break
        case "Invitation":
            break
        case "JoinGroup":
            await handleJoinGroup(json, data)
            break
        case "Message":
            handleNewMessage(json, data)
            await completeMessage(json)
            break
        case "SetMessage":
            break
        case "UserUpdate":
            break
        case "FriendUpdate":
            handleFriendUpdate(data)
            break
        case "GroupUpdate":
            await handleGroupUpdate(json, data)
            await completeMessage(json)
            break
        case "KickOff":
            break
        case "ServiceInfo":
            break
        case "Result":
            break
        case "EncData":
            await completeMessage(json)
            break
        case "GroupInfo":
            await handleGroupInfo(data)
            break
        case "UserInfo":
            handleUserInfo(data)
            break
        case "MemberInfo":
            await handleMemberInfo(json, data["userList"] as? [[String:Any]] ?? [])
            break
        default:
            break
        }
    }
    func handleAddFriend(_ json:[String:Any], _ data:[String:Any]) async {
        let request = gosnRequest.fromJson(OsnRequestType.Friend, json, data)
        if(request.to != mOsnId){
            return
        }
        if gosnFriend.getFriendInfo(request.from) != nil {
            _ = await gosnRequest.acceptFriend(request)
        }else{
            Task {_ = await gosnUser.fetchUserInfo(request.from)}
            _ = gosnRequest.setRequestInfo(request)
        }
        gosnFriend.setAddFreiendCount() // 有新的好友
        geventService.post(.FriendListUpdated, "")
    }
    func handleMemberInfo(_ json:[String:Any], _ userList:[[String:Any]]) async {
        let groupId = json["from"] as? String ?? ""
        if gosnGroup.getGroupInfo(groupId) == nil {
            _ = await gosnGroup.fetchGroupInfo(groupId)
        }
        for user in userList {
            let m = gosnMember.getMemberInfo(groupId, user["osnID"] as? String ?? "")
            let info = gosnMember.fromJson(user, m)
            _ = gosnMember.setMemberInfo(info)
        }
    }
    func handleJoinGroup(_ json:[String:Any], _ data:[String:Any]) async {
        let request = gosnRequest.fromJson(OsnRequestType.ApplyMember, json, data)
        _ = gosnRequest.setRequestInfo(request)
        guard gosnGroup.getGroupInfo(request.group) != nil else {
            Task{await gosnGroup.fetchGroupInfo(request.group)}
            return
        }
    }
    func handleNewMessage(_ json:[String:Any], _ data:[String:Any]){
        if let message = gosnMessage.getMessageFromHash(json["hash"] as? String ?? "") {
            print("dup message: \(message.msgHash)")
            return
        }
        
        
        let message = gosnMessage.fromJson(json, data)
        _ = gosnMessage.setMessage(message)
        gosnConversation.updateConversationForMessage(message)
        geventService.post(OsnEvent.ReceiveMessages, message)

        let con = gosnConversation.getConversationInfo(message.target)
        if !con!.isSilent && UserDefaults.standard.bool(forKey: "kIsBackground") {
            geventService.postNotifcation()
        }
    }
    func handleFriendUpdate(_ data:[String:Any]) {
        let friend = gosnFriend.fromJson(data)
        if(friend.state == OsnFriendState.Deleted){
            return
        }
        if let localFriend = gosnFriend.getFriendInfo(friend.friendId){
            if friend.state != OsnFriendState.Normal {localFriend.state = friend.state}
            if !friend.alias.isEmpty {localFriend.alias = friend.alias}
            _ = gosnFriend.setFriendInfo(localFriend)
        }else{
            _ = gosnFriend.setFriendInfo(friend)
        }
//        let info:any = {}
//        friend.alias && (info.alias = friend.alias)
//        friend.state && (info.state = friend.state)
//        gosnFriend.updateRelateUserInfo(friend.friendId, info)
        geventService.post(OsnEvent.FriendInfoUpdated, friend.friendId)
    }
    func handleGroupUpdate(_ json:[String:Any], _ data:[String:Any]) async {
        let state = data["state"] as? String ?? ""
        let keys = data["infoList"] as? [String] ?? []
        let info = gosnGroupUpdate.fromJson(state, json, data)
        if gosnGroup.getGroupInfo(info.groupID) == nil {
            _ = await gosnGroup.fetchGroupInfo(info.groupID)
        }
        if(state == "NewlyGroup"){
            await gosnGroupUpdate.groupUpdateNewlyGroup(info)
        }else if(state == "UpdateGroup"){
            gosnGroupUpdate.groupUpdateInfo(info, keys)
        }else if(state == "UpdateMember"){
            gosnGroupUpdate.groupUpdateGroupMember(info, keys);
        }else if(state == "DelMember"){
            gosnGroupUpdate.groupUpdateDelMember(info, state)
        }else if(state == "AddMember"){
            await gosnGroupUpdate.groupUpdateAddMember(info)
        }else if(state == "QuitGroup"){
            gosnGroupUpdate.groupUpdateQuitGroup(info)
        }else if(state == "DelGroup"){
            gosnGroupUpdate.groupUpdateDelGroup(info)
        }else if(state == "Mute" || state == "Allow"){
            gosnGroupUpdate.groupUpdateMute(info)
        }else if(state == "AddAdmin"){
            gosnGroupUpdate.groupUpdateAddAdmin(info)
        }else if(state == "DelAdmin"){
            gosnGroupUpdate.groupUpdateDelAdmin(info)
        }
    }
    func handleUserInfo(_ data:[String:Any]) {
        let u = gosnUser.getUserInfo(data["userID"] as? String ?? "")
        let user = gosnUser.fromJson(data, u)
        _ = gosnUser.setUserInfo(user)
        geventService.post(OsnEvent.UserInfoUpdated, user)
    }
    func handleGroupInfo(_ data:[String:Any]) async {
        let g = gosnGroup.getGroupInfo(data["groupID"] as? String ?? "")
        let group = gosnGroup.fromJson(data, g)
        _ = gosnGroup.setGroupInfo(group)
        if(g == nil){
            _ = await gosnMember.updateMemberZone(group.groupId, 0, 50)
        }
        geventService.post(OsnEvent.GroupInfoUpdated, group)
    }
    func sendMessage(_ osnId:String, _ content:OsnMessageContent) async ->(DMCCMessage, OspnError?) {
        let message = DMCCMessage()
        message.target = osnId
        message.originalUser = mOsnId
        message.direction = OsnMessageDirection.Send
        message.status = OsnMessageStatus.Sending
        message.msgType = content.contentType
        message.content = content
        let data:[String:Any] = [
            "content": OsnUtils.toString(gosnContent.toJson(content, isDB: false)) ?? ""
        ]
        print("\(data)")
        guard let json = OsnUtils.makeMessage(command: "Message", from: mOsnId, to: osnId, data: data, key: mOsnKey) else {
            return (message, OspnError.Encode)
        }
        message.timestamp = json["timestamp"] as! Int64
        message.msgHash = json["hash"] as! String
        message.msgHasho =  json.keys.contains("hash0") ? json["hash0"]  as! String : json["hash"]  as! String
        _ = gosnMessage.setMessage(message)
        
        let (_,err) = await gnetService.sendPackage(package: json)
        if err != nil {
            message.status = OsnMessageStatus.Send_Failure
        }else{
            message.status = OsnMessageStatus.Sent
        }
        _ = gosnMessage.setMessage(message)
        gosnConversation.updateConversationForMessage(message)
        return (message, nil)
    }
    func isSuccess(_ json:[String:Any]) -> Bool {
        if !json.keys.contains("errCode") {
            return true
        }
        let errCode = json["errCode"] as? String ?? ""
        return errCode == "success" || errCode == "0:success"
    }
    func errCode(_ json:[String:Any]) -> String {
        if (!json.keys.contains("errCode")){
            return "none";
        }
        return json["errCode"] as? String ?? ""
    }
}
let gimService = OspnImService()
